// 国信证券：518800309526

/**
 * linux 安装 puppeteer
 * cnpm i puppeteer@19.8.3 -g
 * sudo yum install libatk-1.0.so.0
 * yum install gtk3 clang dbus-devel gtk2-devel xorg-x11-server-utils libcap-devel cups-devel libXtst-devel alsa-lib-devel libXrandr-devel nss-devel
 */

const qs = require("qs")
const syncRequest = require("sync-request")
const axios = require("axios")
// const puppeteer = require("puppeteer")

const $calculate = require("./calculate")

// let browser = puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--incognito'], ignoreDefaultArgs: ['--enable-automation'] }).then(e => browser = e)

class Stock {
    /**
     * 以 `${code}|${type}` 作为 key 值，存储 name, market_type
     */
    static cache = {}
    /**
     * 初始化
     * @param type (stock:个股 | index:指数)
     * @param options 指定 options: { name, market_type } 可以忽略调用接口获取 name, market_type
     */
    constructor({ code = "000001", type = "stock", options = null }) {
        let cacheKey = `${code}|${type}`
        this.code = code
        this.type = type

        if (options) {
            let { name = code, market_type = "ab" } = options
            Stock.cache[cacheKey] = {
                name,
                market_type
            }
        }

        if (!Stock.cache[cacheKey]) {
            let { Result: { stock = [] } } = JSON.parse(syncRequest("GET", `http://finance.pae.baidu.com/selfselect/sug?wd=${code}&skip_login=1&finClientType=pc`).getBody().toString())
            let { name, market: market_type } = stock.find(item => item.type == type)
            Stock.cache[cacheKey] = {
                name,
                market_type
            }
        }

        let { name, market_type } = Stock.cache[cacheKey]

        this.name = name
        this.market_type = market_type

    }
    /**
     * 获取分时数据
     */
    async getRealtime() {
        let result = {}

        let { code, type, name, market_type } = this

        let group = { stock: "quotation_minute_ab", index: "quotation_index_minute" }[type]

        let data = {
            all: 1,
            srcid: 5353,
            pointType: "string",
            group,
            code,
            market_type
        }

        let { Result: { cur: current, update: { stockStatus }, newMarketData: { marketData = [], maxPoints = 0 } } } = await Stock.request({ url: `http://finance.pae.baidu.com/vapi/v1/getquotation`, data })

        let latest = marketData.at(-1)

        result.list = []
        latest.p.split(";").forEach((item) => {
            let [timestamp, time, price, avgPrice, range, ratio, volume, amount, totalVolume, totalAmount] = item.split(",")
            // ["时间戳","时间","价格","均价","涨跌额","涨跌幅","成交量","成交额","累积成交量","累积成交额"]
            result.list.push([Number(price), Number(volume)])
        })

        result.code = code
        result.type = type
        result.name = name
        result.current = current
        result.maxPoints = Number(maxPoints)
        result.stockStatus = stockStatus

        return result
    }
    /**
     * 获取K线交易数据
     * @param type(stock:个股 | index:指数)
     * @returns []
     */
    async getTransaction({ ktype = "day" }) {
        let result = []
        let { code, type, market_type } = this

        let group = { stock: "quotation_kline_ab", index: "quotation_index_kline" }[type]

        let data = {
            srcid: 5353,
            pointType: "string",
            group,
            code,
            market_type,
            newFormat: 1,
            is_kc: 0,
            ktype,
            finClientType: "pc"
        }

        let { Result: { newMarketData: { marketData = "" } } } = await Stock.request({ url: `http://finance.pae.baidu.com/vapi/v1/getquotation`, data })
        marketData.split(";").forEach((item, index) => {
            let [timestamp, time, open, close, volume, high, low, amount, range, ratio, turnoverratio, preClose, ma5avgprice, volume5, ma10avgprice, volume10, ma20avgprice, ma20volume] = item.split(",")
            //  ["时间戳", "时间", "开盘", "收盘", "成交量", "最高", "最低", "成交额", "涨跌额", "涨跌幅", "换手率", "昨收", "ma5均价", "ma5成交量", "ma10均价", "ma10成交量", "ma20均价", "ma20成交量"]
            result.push({
                // timestamp, 
                index,
                time,
                open: Number(open),
                close: Number(close),
                high: Number(high),
                low: Number(low),
                volume: Number(volume)
            })
        })

        return result
    }
    /**
     * puppeteer 请求
     */
    static async request({ url, data, resolve }) {
        if (!resolve) return new Promise(resolve => {
            Stock.request({ url, data, resolve })
        })

        let result = null

        let requestUrl = `${url}${url.indexOf("?") == -1 ? "?" : "&"}${qs.stringify(data)}`
        console.log(requestUrl)

        result = (await axios({
            url: requestUrl,
            headers: {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36",
                "accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
                "accept-language": "zh-CN,zh;q=0.9",
                "cache-control": "no-cache",
                "pragma": "no-cache",
                "priority": "i",
                "sec-ch-ua": "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"",
                "sec-ch-ua-mobile": "?0",
                "sec-ch-ua-platform": "\"Windows\"",
                "sec-fetch-dest": "image",
                "sec-fetch-mode": "no-cors",
                "sec-fetch-site": "same-site",
                "Referer": "https://gushitong.baidu.com/"
            }
        })).data

        /*
        let page = null
        try {
            let context = await browser.createIncognitoBrowserContext()
            page = await context.newPage()
            // await page.setCacheEnabled(false)
            await page.goto(requestUrl, { timeout: 10000 })
            result = JSON.parse(await page.evaluate(() => document.body.innerText))
        }
        catch (e) { }
        finally {
            page && await page.close()
        }
        */

        if (result) resolve(result)
        else Stock.request({ url, data, resolve })
    }
    /**
     * @description 生成详情
     * @returns details
     */
    async generateDetails({ transaction = [] }) {
        let result = {}
        let { code, type, name } = this

        transaction.forEach((item, index) => {
            [5, 10, 20, 30, 60, 90, 120, 250].forEach(quantity => {
                item[`ma${quantity}`] = "--"
                if (index < quantity) return false
                let ma = 0
                let volume = 0
                if (quantity <= 10) {
                    for (let j = 0; j < quantity; j++) {
                        ma += transaction[index - j].close
                        volume += transaction[index - j].volume
                    }
                    item[`volume${quantity}`] = Number((volume / quantity).toFixed(2))
                }
                else {
                    for (let j = 0; j < quantity; j++) ma += transaction[index - j].close
                }
                item[`ma${quantity}`] = Number((ma / quantity).toFixed(2))
            })
        })

        let macd = $calculate.macd({ data: transaction })
        let kd = $calculate.kd({ data: transaction })

        result.code = code
        result.type = type
        result.name = name
        result.transaction = transaction
        result.calculate = { macd, kd }

        return result
    }
    /**
     * 搜索股票
     */
    static async search({ word = "" }) {
        let result = []
        if (word) {
            let { Result: { stock = [] } } = await Stock.request({ url: `http://finance.pae.baidu.com/selfselect/sug?wd=${word}&skip_login=1&finClientType=pc` })
            result = stock.filter(item => item.type != "fund")
        }
        return result
    }
    /**
     * 修改K线
     * @returns transaction
     */
    static editTransaction({ transaction = [], editTransaction = { index: -1, open: -1, close: -1, high: -1, low: -1 } }) {
        let result = JSON.parse(JSON.stringify(transaction))

        let { index, open, close, high, low } = editTransaction

        let currentTransaction = result.find(item => item.index == index)

        currentTransaction.open = open == -1 ? currentTransaction.open : open
        currentTransaction.close = close == -1 ? currentTransaction.close : close
        currentTransaction.high = high == -1 ? currentTransaction.high : high
        currentTransaction.low = low == -1 ? currentTransaction.low : low

        currentTransaction.isEdit = true

        return result
    }
    /**
     * 加入一条新的K线
     * @returns transaction
     */
    static pushTransaction({ transaction = [], pushTransaction = { open: -1, close: -1, high: -1, low: -1 } }) {
        let result = JSON.parse(JSON.stringify(transaction))

        let { open, close, high, low } = pushTransaction
        let latestTransaction = { ...result.at(-1) }
        let latestTransactionClose = latestTransaction.close

        latestTransaction.index++
        latestTransaction.open = open == -1 ? latestTransactionClose : open
        latestTransaction.close = close == -1 ? latestTransactionClose : close
        latestTransaction.high = high == -1 ? latestTransactionClose : high
        latestTransaction.low = low == -1 ? latestTransactionClose : low

        latestTransaction.isPush = true

        result.push(latestTransaction)

        return result
    }
    /**
     * @description 获取指定 details 的最新 n 条数据
     * @returns details
     */
    static getDetailsLatest({ details = {}, latest = 60 }) {
        let result = {}
        let { transaction = [], calculate = {} } = details

        let newTransaction = transaction.slice(-latest)
        let newCalculate = { macd: {}, kd: {} }

        for (let attr in calculate.macd) newCalculate.macd[attr] = calculate.macd[attr].slice(-latest)
        for (let attr in calculate.kd) newCalculate.kd[attr] = calculate.kd[attr].slice(-latest)

        result = { ...details, transaction: newTransaction, calculate: newCalculate }

        return result
    }
    /**
     * @description 获取当前 details 中是否出现底背离、顶背离
     * @returns { bottom: null, top: null, latest: "" }
     */
    static divergence({ details = {} }) {
        let result = { bottom: null, top: null, latest: "" }
        let { transaction = [], calculate: { macd: { macd = [] } } } = details

        result.bottom = (() => {
            let result = null
            let macdMin = { macd: -1, index: -1 } // macd 数组中绿色柱子 最大值 与 index 
            macd.forEach((item, index) => (index == 0 || item < macdMin.macd) && (macdMin.macd = item, macdMin.index = index))

            let transactionLowMin = { low: transaction[macdMin.index].low, index: macdMin.index }
            for (let i = macd.length - 1; i > macdMin.index; i--) {
                // 向左找 <- 如果当前位置的 最低价格 小于macd中最大绿色柱子的价格
                if (transaction[i].low < transactionLowMin.low) {
                    transactionLowMin.low = transaction[i].low
                    transactionLowMin.index = i
                }
            }

            if (macdMin.index != transactionLowMin.index) {
                result = {
                    range: [], // 出现底背离的范围
                    ago: 0 // 距离最后一条记录有多少条
                }
                for (let i = macdMin.index, l = transactionLowMin.index; i <= l; i++) {
                    result.range.push(i)
                }
                result.ago = transaction.length - 1 - result.range.at(-1)
            }

            return result
        })()


        result.top = (() => {
            let result = null
            let macdMax = { macd: -1, index: -1 } // macd 数组中红色柱子 最大值 与 index 
            macd.forEach((item, index) => (index == 0 || item > macdMax.macd) && (macdMax.macd = item, macdMax.index = index))

            let transactionHighMax = { high: transaction[macdMax.index].high, index: macdMax.index }
            for (let i = macd.length - 1; i > macdMax.index; i--) {
                // 向左找 <- 如果当前位置的 高价格 大于macd中最大红色柱子的价格
                if (transaction[i].high > transactionHighMax.high) {
                    transactionHighMax.high = transaction[i].high
                    transactionHighMax.index = i
                }
            }

            if (macdMax.index != transactionHighMax.index) {
                result = {
                    range: [], // 出现底背离的范围
                    ago: 0 // 距离最后一条记录有多少条
                }
                for (let i = macdMax.index, l = transactionHighMax.index; i <= l; i++) {
                    result.range.push(i)
                }
                result.ago = transaction.length - 1 - result.range.at(-1)
            }

            return result
        })()


        return result
    }
    /**
     * @description 获取当前 details 中最新的 kd 值
     * @returns { k:0, d:0, j:0 }
     */
    static latestKd({ details = {} }) {
        let result = null
        let { kd } = details.calculate
        result = {
            k: kd.k.at(-1),
            d: kd.d.at(-1)
        }
        return result
    }
    /**
     * @description 获取当前 details 中最新的 macd 值
     * @returns { macd:0, diff:0, dea:0 }
     */
    static latestMacd({ details = {} }) {
        let result = null
        let { macd } = details.calculate
        result = {
            macd: macd.macd.at(-1),
            diff: macd.diff.at(-1),
            dea: macd.dea.at(-1)
        }
        return result
    }
    /**
     * 计算出离 ma 的距离
     */
    static latestMaDifference({ details = {}, ma = 5 }) {
        let result = { float: 0, percentage: 0 }
        let { transaction = [] } = details
        let latest = transaction.at(-1)
        let target = latest[`ma${ma}`]
        let close = latest.close

        result.float = target == "--" ? 0 : Number(Math.abs(target - close).toFixed(2))
        result.percentage = target == "--" ? 0 : Number((((close - target) / target) * 100).toFixed(2))

        return result
    }
    /**
     * 获取所有个股列表
     */
    static async getIndividualList() {
        let result = []

        /*
        let data = await Stock.request({ url: `http://api.mairui.club/hslt/list/b997d4403688d5e66a` })
        result = data.map(({ dm, mc }) => ({ code: dm.split(".")[0], name: mc }))

        // 把有ST的退市的去掉
        result = result.filter((item) => !/ST|退/.test(item.name))
        */

        result = require("../../cache/stockList.json")

        return result
    }

}

module.exports = Stock